/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022.
 * Description: add hpvisor dfx
 * Author: lilinjie8 <lilinjie8@huawei.com>
 * Create: 2022-02-07
 */

#include "dfx.h"
#include <linux/types.h>
#include <linux/rtos_sysctl.h>
#include <linux/set_kbox_region.h>

#ifdef CONFIG_RTOS_HAL_HPVISOR_STAGE2_MEM
unsigned int sysctl_hpvisor_mmu_debug;
#endif
#ifdef CONFIG_RTOS_HAL_HPVISOR_IRQ_BYPASS
unsigned int sysctl_hpvisor_irq_debug;
#endif
#ifdef CONFIG_RTOS_HAL_HPVISOR_VNOTIFY
unsigned int sysctl_hpvisor_vnotify_debug;
#endif
#ifdef CONFIG_RTOS_HAL_HPVISOR_DFX_ENHANCE
unsigned int sysctl_hpvisor_dfx_debug;

#ifdef CONFIG_ARM64
#define MAX_TIME_MS (60 * 1000)
/* hrtimer unit: ms */
static unsigned int sysctl_hpvisor_dfx_hrtimer_debug;
static unsigned int time_low_ms;
static unsigned int time_high_ms = MAX_TIME_MS;

#define DFX_FITER_MAX_EC ((1UL << ESR_ELx_EC_WIDTH) - 1)
#define EC_FILTER_BUFF_LEN 50
static char sysctl_hpvisor_ec_filter_buff[EC_FILTER_BUFF_LEN];

#define DFX_EC_FILTER_COUNT 10
static unsigned long ec_filter_arr[DFX_EC_FILTER_COUNT];
static int ec_filter_count;

static unsigned long sysctl_hpvisor_dfx_vcpu_filter;

static int fetch_ec(char *startptr, char **endptr, unsigned long *val)
{
/* max EC is 0x3f, the max length is 4 */
#define MAX_EC_LENGTH 4
	int ec_len;
	char *ptr = NULL;
	char tmpstr[MAX_EC_LENGTH + 1] = {0};

	ptr = strchr(startptr, ',');
	if (!ptr) {
		*endptr = startptr + strlen(startptr);
		ec_len = *endptr - startptr;
		/*
		 * Except for the trailing \0 character, we allow the last character
		 * of the string to be \n
		 */
		if (startptr[strlen(startptr) - 1] == '\n')
			ec_len--;
	} else {
		*endptr = ptr;
		ec_len = *endptr - startptr;
	}

	/* a valid EC need start with 0x, the min length is 3 */
	if (ec_len < 3 || ec_len > MAX_EC_LENGTH) {
		pr_err("invalid input EC, length error\n");
		return -EINVAL;
	}

	strncpy(tmpstr, startptr, ec_len);
	tmpstr[ec_len] = '\0';

	/* parse string to 16 base number */
	if (kstrtoul(tmpstr, 16, val) != 0) {
		pr_err("EC should be a valid 16 base number\n");
		return -EINVAL;
	}
#undef MAX_EC_LENGTH
	return 0;
}

static int parse_ec_filter(char *buffer)
{
	unsigned long tmp_ec;
	char *startptr = buffer;
	char *endptr = NULL;
	int count = 0;
	unsigned long tmp_ec_arr[DFX_EC_FILTER_COUNT] = {0};

	if (*buffer == '\n') {
		ec_filter_count = 0;
		memset(ec_filter_arr, 0, sizeof(ec_filter_arr));
		return 0;
	}

	do {
		if (count >= DFX_EC_FILTER_COUNT) {
			pr_err("we support maximum of %d ECs to be filtered\n",
				DFX_EC_FILTER_COUNT);
			return -EINVAL;
		}

		if (fetch_ec(startptr, &endptr, &tmp_ec) != 0)
			return -EINVAL;

		if (tmp_ec > DFX_FITER_MAX_EC) {
			pr_err("invalid EC, input EC %#lx need smaller than %#lx\n", tmp_ec,
				DFX_FITER_MAX_EC);
			return -EINVAL;
		}

		tmp_ec_arr[count++] = tmp_ec;
		startptr = endptr + 1;
	} while (*endptr == ',');

	if (*endptr != '\0' && *endptr != '\n') {
		pr_err("invalid input ECs\n");
		return -EINVAL;
	}

	ec_filter_count = count;
	memcpy(ec_filter_arr, tmp_ec_arr, sizeof(tmp_ec_arr));
	return 0;
}

static int proc_dostring_ec_filter(struct ctl_table *table, int write,
	void *buffer, size_t *lenp, loff_t *ppos)
{
	int error;

	if (write != 0) {
		error = parse_ec_filter(buffer);
		if (error)
			return error;
	}
	return proc_dostring(table, write, buffer, lenp, ppos);
}

bool dfx_vcpu_filter(struct kvm_vcpu *vcpu)
{
	if (sysctl_hpvisor_dfx_vcpu_filter == 0)
		return true;

	return ((sysctl_hpvisor_dfx_vcpu_filter >> vcpu->vcpu_id) & 1) == 1;
}

bool dfx_ec_filter(struct kvm_vcpu *vcpu)
{
	int i;
	unsigned long ec;

	if (ec_filter_count == 0)
		return true;

	ec = ESR_ELx_EC(kvm_vcpu_get_esr(vcpu));
	for (i = 0; i < ec_filter_count; i++)
		if (ec_filter_arr[i] == ec)
			break;

	if (i == ec_filter_count)
		return false;

	return true;
}

void filter_hpvisor_dfx_switch_debug(struct kvm_vcpu *vcpu, bool enter)
{
	if (!dfx_vcpu_filter(vcpu))
		return;

	if (!dfx_ec_filter(vcpu))
		return;

	hpvisor_dfx_switch_debug(vcpu, enter);
}
#endif
#endif

static struct ctl_table hpviosr_hash_sysctls[] = {
#ifdef CONFIG_RTOS_HAL_HPVISOR_STAGE2_MEM
	{
		.procname       = "hpvisor_mmu_debug",
		.data           = &sysctl_hpvisor_mmu_debug,
		.maxlen         = sizeof(unsigned int),
		.mode           = 0640,
		.proc_handler   = proc_dointvec,
	},
#endif
#ifdef CONFIG_RTOS_HAL_HPVISOR_IRQ_BYPASS
	{
		.procname       = "hpvisor_irq_debug",
		.data           = &sysctl_hpvisor_irq_debug,
		.maxlen         = sizeof(unsigned int),
		.mode           = 0640,
		.proc_handler   = proc_dointvec,
	},
#endif
#ifdef CONFIG_RTOS_HAL_HPVISOR_VNOTIFY
	{
		.procname       = "hpvisor_vnotify_debug",
		.data           = &sysctl_hpvisor_vnotify_debug,
		.maxlen         = sizeof(unsigned int),
		.mode           = 0640,
		.proc_handler   = proc_dointvec,
	},
#endif
#ifdef CONFIG_RTOS_HAL_HPVISOR_DFX_ENHANCE
	{
		.procname       = "hpvisor_dfx_debug",
		.data           = &sysctl_hpvisor_dfx_debug,
		.maxlen         = sizeof(unsigned int),
		.mode           = 0640,
		.proc_handler   = proc_dointvec,
	},
#ifdef CONFIG_ARM64
	{
		.procname       = "hpvisor_dfx_vcpu_filter",
		.data           = &sysctl_hpvisor_dfx_vcpu_filter,
		.maxlen         = sizeof(sysctl_hpvisor_dfx_vcpu_filter),
		.mode           = 0640,
		.proc_handler   = proc_doulongvec_minmax,
	},
	{
		.procname       = "hpvisor_dfx_ec_filter",
		.data           = sysctl_hpvisor_ec_filter_buff,
		.maxlen         = sizeof(sysctl_hpvisor_ec_filter_buff),
		.mode           = 0640,
		.proc_handler   = proc_dostring_ec_filter,
	},
	{
		.procname       = "hpvisor_dfx_hrtimer_debug",
		.data           = &sysctl_hpvisor_dfx_hrtimer_debug,
		.maxlen         = sizeof(sysctl_hpvisor_dfx_hrtimer_debug),
		.mode           = 0640,
		.proc_handler   = proc_dointvec_minmax,
		.extra1         = &time_low_ms,
		.extra2         = &time_high_ms,
	},
#endif
#endif
	{}
};

rtos_debug_sysctls_proc_init(hpvisor, hpviosr_hash_sysctls);

#define HPVISOR_PRINTK_BUFFER_SIZE	512

static DEFINE_PER_CPU(char[HPVISOR_PRINTK_BUFFER_SIZE], hpvisor_kbox_logbuf);

static int printk_in_kbox(const char *fmt, va_list args)
{
	int text_len = 0;
	char *logbuf = NULL;
	const char *skip_header;

	logbuf = get_cpu_ptr(hpvisor_kbox_logbuf);
	text_len = vscnprintf(logbuf, HPVISOR_PRINTK_BUFFER_SIZE, fmt, args);

	skip_header = printk_skip_level(logbuf);
	/*
	 * No need to check length here because vscnprintf
	 * put '\0' at the end of the string.
	 */
	text_len -= skip_header - logbuf;
	kern_kbox_write(get_kbox_region(), skip_header, text_len);
	put_cpu_ptr(logbuf);

	return text_len;
}

int hpvisor_printk(const char *fmt, ...)
{
	int r;
	va_list args;

	va_start(args, fmt);
	r = printk_in_kbox(fmt, args);
	va_end(args);

	return r;
}

#ifdef CONFIG_ARM64
#ifdef CONFIG_RTOS_HAL_HPVISOR_DFX_ENHANCE
#include <linux/sched/debug.h>
#include "hrtimer.h"

#define MAX_SHOW_STACK_TIMES 10

void set_hpvisor_dfx_hrtimer_debug(unsigned int time_value)
{
	sysctl_hpvisor_dfx_hrtimer_debug = time_value;
}

static enum hrtimer_restart hrtimer_handler(struct hrtimer *timer)
{
	struct kvm_vcpu *vcpu = container_of(timer, struct kvm_vcpu, hpv_timer.timer);
	struct hpvisor_hrtimer *hpv_timer = &vcpu->hpv_timer;

	if (!hpv_timer->task) {
		hpvisor_err("task is invalid\n");
		return HRTIMER_NORESTART;
	}

	hpvisor_info("------------- hpvisor hrtimer info begein -------------\n"
		"hcr_el2:0x%llx gic mode:%lu cpu:%d ec:0x%lx pc:0x%llx lr:0x%llx\n"
		"spsr_el1:0x%llx esr_el1:0x%llx sp_el1:0x%llx elr_el1:0x%llx\n"
		"spsr_el2:0x%llx esr_el2:0x%x far_el2:0x%lx mdcr_el2:0x%x vcpu_id:%d\n",
		vcpu->arch.hcr_el2, vcpu->gic_mode, vcpu->cpu, ESR_ELx_EC(kvm_vcpu_get_esr(vcpu)),
		vcpu_gp_regs(vcpu)->pc, vcpu_gp_regs(vcpu)->regs[REG_LR_IDX],
		__vcpu_sys_reg(vcpu, SPSR_EL1),
		__vcpu_sys_reg(vcpu, ESR_EL1), __vcpu_sys_reg(vcpu, SP_EL1),
		__vcpu_sys_reg(vcpu, ELR_EL1), vcpu_gp_regs(vcpu)->pstate, kvm_vcpu_get_esr(vcpu),
		kvm_vcpu_get_hfar(vcpu), vcpu->arch.mdcr_el2, vcpu->vcpu_id);
	sched_show_task(hpv_timer->task);

	hpvisor_info("current task stack is following\n");
	sched_show_task(current);
	hpvisor_info("------------- hpvisor hrtimer info end -------------\n");

	if (++hpv_timer->show_stack_times >= MAX_SHOW_STACK_TIMES) {
		hpvisor_err("show stack for (%s|%d) has reached %d times, do not show stack again\n",
			hpv_timer->task->comm, hpv_timer->task->pid, MAX_SHOW_STACK_TIMES);
		return HRTIMER_NORESTART;
	}
	hrtimer_forward_now(&hpv_timer->timer, ns_to_ktime(hpv_timer->applied_time));

	return HRTIMER_RESTART;
}

void hpvisor_hrtimer_init(struct hpvisor_hrtimer *hpv_timer)
{
	hpv_timer->stopped = true;
	hpv_timer->applied_time = 0;
	hpv_timer->task = NULL;
	hpv_timer->show_stack_times = 0;
}

void hpvisor_hrtimer_start(struct hpvisor_hrtimer *hpv_timer, struct task_struct *task)
{
	hpv_timer->applied_time = sysctl_hpvisor_dfx_hrtimer_debug * NSEC_PER_MSEC;
	if (hpv_timer->applied_time == 0 || !hpv_timer->stopped)
		return;

	hrtimer_init(&hpv_timer->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	hpv_timer->task = task;
	hpv_timer->timer.function = hrtimer_handler;
	hpv_timer->show_stack_times = 0;

	hrtimer_start(&hpv_timer->timer, ns_to_ktime(hpv_timer->applied_time), HRTIMER_MODE_REL);
	hpv_timer->stopped = false;
}

void hpvisor_hrtimer_cancel(struct hpvisor_hrtimer *hpv_timer)
{
	if (hpv_timer->stopped)
		return;

	hrtimer_cancel(&hpv_timer->timer);
	hpv_timer->stopped = true;
	hpv_timer->task = NULL;
}
#endif
#endif
